﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using GameLib;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using GameLib.Utils;
using Sandbox.Scene.Elements;
using GameLib.WorldMap;

namespace Sandbox.Scene
{
	public enum MapSceneObjectLayers
	{
		AboveTerrain,
		AboveGrids,
		AboveOverlay,
		ReferenceImages,
	}

	public class MapScene
	{
		public MapScene(Grid parentPanel)
		{
			MapCanvas = new Canvas();

			MapCanvas.RenderTransform = new ScaleTransform(m_MapScale, m_MapScale);

			m_ParentPanel = parentPanel;
			parentPanel.Children.Add(MapCanvas);

			m_ParentPanel.SizeChanged += new SizeChangedEventHandler(ParentPanel_SizeChanged);
			
			IsRendering = false;


			m_Cursor = new MapCursor();
			AddSceneObject(m_Cursor);
		}

		void ParentPanel_SizeChanged(object sender, SizeChangedEventArgs e)
		{
			Redraw();
		}


		public Map Map
		{
			get { return m_Map; }
			set
			{
				if (m_Map != value && value != null)
				{
					m_Map = value;
					OnMapChanged();
				}
			}
		}

		public int GetHorizontalSpan(int tileCount)
		{
			return HexWidth * 3 / 4 * tileCount + HexWidth / 4;
		}

		public int GetVerticalSpan(int tileCount)
		{
			return HexHeight * tileCount;
		}


		private void OnMapChanged()
		{
			var w = m_Map.AlignedWidth;
			var h = m_Map.AlignedHeight;

			m_PhysicalSize.Width = GetHorizontalSpan(w);//(HexWidth / 4 * 3) * w + (HexWidth / 4);
			m_PhysicalSize.Height = GetVerticalSpan(h); // HexHeight* h; //+ HexHeight / 2;

			m_Tiles = new TileSceneObjectCollection(w, h);

			for (int y = 0; y < h; y++)
			{
				for (int x = 0; x < w; x++)
				{
					//foreach (Entity entity in m_Map.GetTile(x, y).Entities)
					//	m_EntitySceneObjects.Add(new EntitySceneObject(this, entity));
					Tile tile = m_Map.GetTileAligned(x, y);
					if (tile != null)
						m_Tiles[x, y] = new TileSceneObject(this, tile);
				}
			}

			foreach (River river in m_Map.Rivers)
			{
				AddRiverObject(river);
			}

			m_Map.RiverEmerged += new RiverEmergedEventHandler(Map_RiverEmerged);

			Display();
		}



		public void Scroll(double x, double y)
		{
			m_TopLeftPt.X = Math.Max(m_TopLeftPt.X + x / m_MapScale, 0);
			m_TopLeftPt.Y = Math.Max(m_TopLeftPt.Y + y / m_MapScale, 0);
			UpdateSceneObjectCanvasTransform();
			Redraw();
		}

		public void ShowCursor(int x, int y)
		{
			m_Cursor.Location = new Point(x, y);
			m_Cursor.Show();
		}

		public void HideCursor()
		{
			if (m_Cursor != null)
				m_Cursor.Hide();
		}

		public int CursorSize
		{
			get { return m_Cursor.Radius; }
			set { m_Cursor.Radius = value; }
		}

		public void AddSceneObject(MapSceneObject sceneObj)
		{
			if (!m_SceneObjects.Contains(sceneObj))
			{
				m_SceneObjects.Add(sceneObj);
				sceneObj.OnAttached(this);
			}
		}

		public void OnTileContentChanged(TileSceneObject tileSceneObj)
		{
			// should defer redraw to next frame
			Redraw();
		}

		public bool IsRendering
		{
			get;
			private set;
		}


		public DrawingGroup TerrainDrawingGroup
		{
			get;
			private set;
		}

		public DrawingGroup OverlayDrawingGroup
		{
			get;
			private set;
		}

		private void Display()
		{
			MapCanvas.Children.Clear();

			IsRendering = true;

			m_SceneObjCanvases = new Canvas[Enum.GetValues(typeof(MapSceneObjectLayers)).Length];


			m_TerrainImage = new Image();
			m_TerrainImage.Stretch = Stretch.None;
			//imageControl.Source = drawingImgSource;

			//m_MapImage.SetValue(Canvas.LeftProperty, x * m_PhysicalSize.Width / divide);
			//m_MapImage.SetValue(Canvas.TopProperty, y * m_PhysicalSize.Height / divide);


			MapCanvas.Children.Add(m_TerrainImage);


			m_OverlayImage = new Image();
			m_OverlayImage.Stretch = Stretch.None;
			MapCanvas.Children.Add(m_OverlayImage);

			CreateSceneObjectCanvas(MapSceneObjectLayers.AboveTerrain);


			/*

			for (int x = 0; x < divide; x++)
				for (int y = 0; y < divide; y++)
				{
					Image imageControl = new Image();
					imageControl.Stretch = Stretch.None;
					imageControl.Source = drawingImgSource;

					imageControl.SetValue(Canvas.LeftProperty, x * m_PhysicalSize.Width / divide);
					imageControl.SetValue(Canvas.TopProperty, y * m_PhysicalSize.Height / divide);


					MapCanvas.Children.Add(imageControl);
				}
			*/
			/*
			int hw = HexWidth * 3 / 4;
			int hh = HexHeight / 2;
			int hs = HexWidth / 2;

			PathFigure pathFigure = new PathFigure();
			pathFigure.StartPoint = new Point(hw * 2, hh * 3);

			BezierSegment seg1 = new BezierSegment();
			seg1.Point1 = new Point(hw * 2, hh * 3);
			seg1.Point2 = new Point(hw * 2 + hs/2, hh * 4+20);
			seg1.Point3 = new Point(hw * 3, hh * 4);
			pathFigure.Segments.Add(seg1);

			BezierSegment seg2 = new BezierSegment();
			seg2.Point1 = new Point(hw * 3 + hs / 2, hh * 5);
			seg2.Point2 = new Point(hw * 4, hh * 5);
			seg2.Point3 = new Point(hw * 6, hh * 7);
			pathFigure.Segments.Add(seg2);

			PathGeometry pathGeo = new PathGeometry();
			pathGeo.Figures.Add(pathFigure);

			Path path2 = new Path();
			path2.Stroke =new SolidColorBrush(Colors.LightYellow);
			path2.Data = pathGeo;
			path2.StrokeThickness = 16;
			MapCanvas.Children.Add(path2);

			Path path = new Path();
			TileBrush riverTile = new ImageBrush(new BitmapImage(new Uri("Content/Images/Map/Elements/water dark_hi.png", UriKind.Relative)));
			riverTile.ViewportUnits = BrushMappingMode.Absolute;
			riverTile.Viewport = new Rect(0, 0, 200, 200);
			riverTile.TileMode = TileMode.Tile;

			path.Stroke = riverTile; //  new SolidColorBrush(Colors.DeepSkyBlue);
			path.Data = pathGeo;
			path.StrokeThickness = 10;
			MapCanvas.Children.Add(path);

			*/

			

			m_Grids = new Rectangle();
			BitmapImage bitmap = new BitmapImage(new Uri("../../Content/Images/Map/Elements/grid.png", UriKind.Relative));
			TileBrush tileBrush = new ImageBrush(bitmap);
			tileBrush.ViewportUnits = BrushMappingMode.Absolute;
			tileBrush.Viewport = new Rect(0, 0, HexWidth / 2 * 3, HexHeight * 2);
			tileBrush.TileMode = TileMode.Tile;
			//tileBrush.Stretch = Stretch.Fill;
			m_Grids.Fill = tileBrush;
			m_Grids.SetValue(Canvas.LeftProperty, (double)(-HexWidth / 4));
			m_Grids.SetValue(Canvas.TopProperty, (double)(-HexHeight / 2));
			m_Grids.Width = m_PhysicalSize.Width + HexWidth / 4;
			m_Grids.Height = m_PhysicalSize.Height + HexHeight / 2;

			MapCanvas.Children.Add(m_Grids);


			CreateSceneObjectCanvas(MapSceneObjectLayers.AboveGrids);


			CreateSceneObjectCanvas(MapSceneObjectLayers.AboveOverlay);


			foreach (MapSceneObject sceneObj in m_SceneObjects)
			{
				sceneObj.Reappear();
			}


			CreateSceneObjectCanvas(MapSceneObjectLayers.ReferenceImages);
			//ReferenceImagesOpacity = 0.5f;

			for (int i = 0; i < Map.ReferenceImageData.Count; i++)
				ReferenceImages.Add(new ReferenceImage(this, i));

			UpdateSceneObjectCanvasTransform();
			Redraw();




			/*
			foreach (var tileObj in AllTileSceneObjects)
			{
				tileObj.Display();
			}*/

			/*
			foreach (var entityObj in Entities)
			{
				entityObj.Display();
			}*/
		}

		private void Redraw()
		{
			Rect viewport = new Rect((int)m_TopLeftPt.X, (int)m_TopLeftPt.Y, m_ParentPanel.ActualWidth / m_MapScale + 1, m_ParentPanel.ActualHeight / m_MapScale + 1);

			TerrainDrawingGroup = new DrawingGroup();
			TerrainDrawingGroup.ClipGeometry = new RectangleGeometry(viewport);

			OverlayDrawingGroup = new DrawingGroup();
			OverlayDrawingGroup.ClipGeometry = new RectangleGeometry(viewport);

			Rect coveringRect = viewport;
			//coveringRect.Width = HexWidth * 5;
			//coveringRect.Height = HexHeight * 5;
			GeometryDrawing rect = new GeometryDrawing(
				new SolidColorBrush(Colors.Transparent),
				null,
				new RectangleGeometry(coveringRect));

			OverlayDrawingGroup.Children.Add(rect);


			//int divide = 16;

			int min_x = Math.Max(((int)viewport.Left) / (HexWidth / 4 * 3) - 1, 0);
			int min_y = Math.Max(((int)viewport.Top) / HexHeight -1, 0);

			int max_x = Math.Min(((int)viewport.Right) / (HexWidth / 4 * 3) + 2, Map.AlignedWidth);
			int max_y = Math.Min(((int)viewport.Bottom) / HexHeight + 2, Map.AlignedHeight);

			for (int y = min_y; y < max_y; y++)
			{
				for (int x = min_x + min_x % 2; x < max_x; x += 2)
					m_Tiles[x, y].Display();

				if (y < Map.AlignedHeight - 1)
				{
					for (int x = min_x + (min_x + 1) % 2; x < max_x; x += 2)
						m_Tiles[x, y].Display();
				}
			}

			DrawingImage drawingImgSource = new DrawingImage(TerrainDrawingGroup);
			drawingImgSource.Freeze();
			m_TerrainImage.Source = drawingImgSource;

			drawingImgSource = new DrawingImage(OverlayDrawingGroup);
			drawingImgSource.Freeze();
			m_OverlayImage.Source = drawingImgSource;
			m_OverlayImage.HorizontalAlignment = HorizontalAlignment.Center;
			

			int gridLeft = -((int)viewport.Left % (HexWidth / 2 * 3)) -HexWidth / 4;
			int gridTop = -((int)viewport.Top % (HexHeight * 2)) -HexHeight / 2;

			m_Grids.SetValue(Canvas.LeftProperty, (double)(gridLeft));
			m_Grids.SetValue(Canvas.TopProperty, (double)(gridTop));
			m_Grids.Width = viewport.Width - gridLeft;
			m_Grids.Height = viewport.Height - gridTop;
		}

		private void CreateSceneObjectCanvas(MapSceneObjectLayers layer)
		{
			Canvas canvas = new Canvas();
			canvas.RenderTransform = new TranslateTransform();
			MapCanvas.Children.Add(canvas);
			m_SceneObjCanvases[(int)layer] = canvas;
		}

		private void UpdateSceneObjectCanvasTransform()
		{
			int canvasCount = Enum.GetValues(typeof(MapSceneObjectLayers)).Length;

			for (int i = 0; i < canvasCount; i++)
			{
				TranslateTransform trans = m_SceneObjCanvases[i].RenderTransform as TranslateTransform;
				trans.X = -m_TopLeftPt.X;
				trans.Y = -m_TopLeftPt.Y;
			}
		}

		private IEnumerable<TileSceneObject> AllTileSceneObjects
		{
			get
			{
				for (int y = 0; y < Map.AlignedHeight; y++)
				{
					for (int x = 0; x < Map.AlignedWidth; x++)
					{
						var tileObj = m_Tiles[x, y];
						if (tileObj != null)
							yield return tileObj;
					}
				}						
			}
		}

		public int MapWidth
		{
			get { return m_Map.Width; }
		}

		public int MapHeight
		{
			get { return m_Map.Height; }
		}

		public Canvas MapCanvas { get; private set; }

		public Canvas GetSceneObjectCanvas(MapSceneObjectLayers layer)
		{
			return m_SceneObjCanvases[(int)layer];
		}

		public Point GetTileTopLeft(int x, int y)
		{
			int left = HexWidth * 3 / 4 * x;
			int top = HexHeight / 2 * y;//HexHeight * y + (x % 2 == 1 ? HexHeight / 2 : 0);
			return new Point(left, top);
		}

		public Point GetTileTopLeftOnCanvas(int x, int y)
		{
			Point absTopLeft = GetTileTopLeft(x, y);
			absTopLeft.Offset(-m_TopLeftPt.X, -m_TopLeftPt.Y);
			return absTopLeft;
		}

		public Point GetTileCenter(int x, int y)
		{
			int centerX = HexWidth * 3 / 4 * x + HexWidth / 2;
			int centerY = HexHeight / 2 * (y + 1);
			return new Point(centerX, centerY);
		}

		public Point GetTileCoordinateAt(Point viewportPt)
		{
			double realX = viewportPt.X / m_MapScale + m_TopLeftPt.X;
			double realY = viewportPt.Y / m_MapScale + m_TopLeftPt.Y;

			int x = MathEx.Clamp(((int)realX - HexWidth / 8) / (HexWidth / 4 * 3), 0, MapWidth-1);
			int shift = x % 2;
			int y = MathEx.Clamp(((int)realY - shift * HexHeight / 2) / HexHeight * 2 + shift, 0, MapHeight);

			/*
			if (x % 2 == 0)
				y = Math.Min(Math.Max(0, (int)realY / HexHeight), MapHeight);
			else
				y = Math.Min(Math.Max(0, ((int)realY - HexHeight / 2) / HexHeight), MapHeight-1);*/

			return new Point(x, y);
		}


		private const double VertexLocTolerance = 0.25;

		public MapVertex? GetVertexAt(Point viewportPt)
		{
			Point mapPt = ViewportToMap(viewportPt);

			double y = Math.Min(Math.Max(0.0, mapPt.Y / (HexHeight / 2)), MapHeight * 2);
			double y_floor = Math.Floor(y);
			double y_ceiling = Math.Ceiling(y);

			if (y - y_floor >= VertexLocTolerance && y_ceiling - y >= VertexLocTolerance)
				return null;

			int vertexY = (y - y_floor <= y_ceiling - y) ? (int)y_floor : (int)y_ceiling;

			double x = Math.Min(Math.Max(-1, (mapPt.X - HexWidth / 8) / (HexWidth / 4 * 3)), MapWidth);

			double x_floor = Math.Floor(x);

			// big cell
			if ((vertexY % 2 == 0) ^ ((int)x_floor % 2 == 0))
				return null;

			double x_ceiling = Math.Ceiling(x);

			int vertexX = (x - x_floor <= x_ceiling - x) ? (int)x_floor : (int)x_ceiling;

			return new MapVertex(vertexX, vertexY);
		}

		public Point ViewportToMap(Point viewportPt)
		{
			double realX = viewportPt.X / m_MapScale + m_TopLeftPt.X;
			double realY = viewportPt.Y / m_MapScale + m_TopLeftPt.Y;
			return new Point(realX, realY);
		}

		public Point GetVertexPoint(MapVertex vert)
		{
			double x = vert.Location.X * (HexWidth / 4 * 3);

			if ((vert.Location.Y % 2 == 0) ^ (vert.Location.X % 2 == 1))
				x += HexWidth / 4;

			double y = vert.Location.Y * HexHeight / 2;

			return new Point(x, y);
		}

		public Point GetVertexPointOnCanvas(MapVertex vert)
		{
			Point vertLoc = GetVertexPoint(vert);
			vertLoc.Offset(-m_TopLeftPt.X, -m_TopLeftPt.Y);
			return vertLoc;
		}

		public ReferenceImage AddReferenceImage(Uri uriSource)
		{
			Map.ReferenceImageData.Add(new MapRefImageData(uriSource.ToString()));
			ReferenceImage newImage = new ReferenceImage(this, Map.ReferenceImageData.Count - 1);
			ReferenceImages.Add(newImage);
			return newImage;
		}

		public List<ReferenceImage> ReferenceImages
		{
			get { return _ReferenceImages; }
		}
		List<ReferenceImage> _ReferenceImages = new List<ReferenceImage>();

		public double ReferenceImagesOpacity
		{
			get { return GetSceneObjectCanvas(MapSceneObjectLayers.ReferenceImages).Opacity; }
			set { GetSceneObjectCanvas(MapSceneObjectLayers.ReferenceImages).Opacity = value; }
		}

		public double MapScale
		{
			get { return m_MapScale; }
		}

		public int HexWidthScaled
		{
			get { return (int)(HexWidth * MapScale); }
		}

		public int HexHeightScaled
		{
			get { return (int)(HexHeight * MapScale); }
		}

		private RiverSceneObject AddRiverObject(River river)
		{
			RiverSceneObject riverObject = new RiverSceneObject(river);
			AddSceneObject(riverObject);
			return riverObject;
		}



		private void  Map_RiverEmerged(River newRiver)
		{
 			AddRiverObject(newRiver);
		}


		private TileSceneObjectCollection m_Tiles;
		private Map m_Map;
		private Size m_PhysicalSize = new Size();
		private Grid m_ParentPanel;
		private Image m_TerrainImage;
		private Image m_OverlayImage;
		private double m_MapScale = 1.0;
		private Point m_TopLeftPt;
		private Rectangle m_Grids;
		private MapCursor m_Cursor;
		private List<MapSceneObject> m_SceneObjects = new List<MapSceneObject>();
		private List<RiverSceneObject> m_RiverObjects = new List<RiverSceneObject>();
		private Canvas[] m_SceneObjCanvases;

		public readonly int HexWidth = 128;
		public readonly int HexHeight = 112;

	}



	public class EntitySceneObjectCollection : List<EntitySceneObject>
	{

	}

	public class TileSceneObjectCollection : TileCollection<TileSceneObject>
	{
		public TileSceneObjectCollection(int width, int height)
			: base(width, height)
		{

		}
	}


}
